package com.nulldev.util.VariableAPI;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.regex.Pattern;

import com.nulldev.util.VariableAPI.util.random.GlobalRandom;
import com.nulldev.util.codecs.uniapi.UniBase64;
import com.nulldev.util.data.Charsets.CharsetUtil;

public class ByteUtil {
	private static final SecureRandom sr = GlobalRandom.getGlobalInsecureRandom();

	public static byte[] create() {
		return new byte[]
			{};
	}

	public static byte[] create(final int size) {
		return new byte[size];
	}

	public static byte[] ensureCapacity(final byte[] array, final int length) {
		return Arrays.copyOf(array, length);
	}

	public static byte[] randomBytes(final int length) {
		if (length <= 0)
			return create();
		byte[] rnd = create(length);
		sr.nextBytes(rnd);
		return rnd;
	}

	public static byte[] concatenateByteArrays(byte[] a, byte[] b) {
		byte[] result = new byte[a.length + b.length];
		System.arraycopy(a, 0, result, 0, a.length);
		System.arraycopy(b, 0, result, a.length, b.length);
		return result;
	}

	public static byte[] add(byte[] data, final byte b) {
		return concatenateByteArrays(data, new byte[]
			{ b });
	}

	public static int unsignedToBytes(final byte b) {
		return b & 0xFF;
	}

	public static byte[] toSigned(final int[] unsignedBytes) {
		final ByteBuffer buf = ByteBuffer.allocate(4 * unsignedBytes.length);
		buf.order(ByteOrder.BIG_ENDIAN);
		for (int i = 0; i < unsignedBytes.length; ++i) {
			buf.putInt(unsignedBytes[i]);
		}
		return buf.array();
	}

	private final static char[] hexArray = "0123456789abcdef".toCharArray();
	public static final byte[] EMPTY_BUF = new byte[0];

	public static String getHex(byte[] bytes) {
		if (bytes == null)
			return "null";
		char[] hexChars = new char[bytes.length * 2];
		for (int j = 0; j < bytes.length; j++) {
			int v = bytes[j] & 0xFF;
			hexChars[j * 2] = hexArray[v >>> 4];
			hexChars[j * 2 + 1] = hexArray[v & 0x0F];
		}
		return new String(hexChars);
	}

	public static int[] convert(final byte buf[]) {
		int intArr[] = new int[buf.length];
		for (int i = 0; i < buf.length; i++) {
			intArr[i] = buf[i];
		}
		return intArr;
	}

	public static byte[] fromHex(final String s) {
		final byte[] b = new byte[s.length() / 2];
		for (int i = 0; i < b.length; i++) {
			final int index = i * 2;
			final int v = Integer.parseInt(s.substring(index, index + 2), 16);
			b[i] = (byte) v;
		}
		return b;
	}

	public static byte[][] divideArray(byte[] source, int chunksize) {
		byte[][] ret = new byte[(int) Math.ceil(source.length / (double) chunksize)][chunksize];

		int start = 0;

		for (int i = 0; i < ret.length; i++) {
			ret[i] = Arrays.copyOfRange(source, start, start + chunksize);
			start += chunksize;
		}

		return ret;
	}

	public static byte[] fromBase64(String data) {
		if (data.startsWith("data:image/png;base64,")) {
			data = data.replaceAll(Pattern.quote("data:image/png;base64,"), "");
		}
		if (data.startsWith("data:image/jpg;base64,")) {
			data = data.replaceAll(Pattern.quote("data:image/jpg;base64,"), "");
		}
		if (data.startsWith("data:image/jpeg;base64,")) {
			data = data.replaceAll(Pattern.quote("data:image/jpeg;base64,"), "");
		}
		if (data.startsWith("data:image/gif;base64,")) {
			data = data.replaceAll(Pattern.quote("data:image/gif;base64,"), "");
		}
		try {
			return UniBase64.decodeToBytes(data);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static byte[] toByte(final Byte[] input) {
		final byte[] out = new byte[input.length];
		System.arraycopy(input, 0, out, 0, out.length);
//		byte[] out = new byte[input.length];
//		for (int i = 0; i < input.length; i++) {
//			final Byte t = input[i];
//			out[i] = t.byteValue();
//		}
		return out;
	}

	public static void zero(final byte[] buf) {
		if (buf == null || buf.length == 0)
			return;
		for (int i = 0; i < buf.length; i++) {
			buf[i] = 0;
		}
	}

	public static byte[] fromCharBuffer(final char[] chars) {
		final CharBuffer charBuffer = CharBuffer.wrap(chars);
		final ByteBuffer byteBuffer = CharsetUtil.UTF_8.encode(charBuffer);
		final byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
		Arrays.fill(byteBuffer.array(), (byte) 0);
		return bytes;
	}

	public static byte[] wsToBytes(final boolean[] input) {
		byte[] toReturn = new byte[input.length / 8];
		for (int entry = 0; entry < toReturn.length; entry++) {
			for (int bit = 0; bit < 8; bit++) {
				if (input[entry * 8 + bit]) {
					toReturn[entry] |= (128 >> bit);
				}
			}
		}
		return toReturn;
	}

	public static boolean[] wsToBitArray(int data, int arraySize) {
		boolean bits[] = new boolean[arraySize];
		while (arraySize != 0 && data != 0) {
			bits[--arraySize] = (data % 2) != 0;
			data /= 2;
		}
		return bits;
	}

	public static boolean wsValidate(byte[] bytes) {
		final CharsetDecoder cs = CharsetUtil.UTF_8.newDecoder();
		try {
			cs.decode(ByteBuffer.wrap(bytes));
			return true;
		} catch (CharacterCodingException e) {
			return false;
		}
	}

	public static byte[] getByteByString(String binaryString) {
		if (binaryString == null || binaryString.isEmpty())
			return null;
		int splitSize = 8;

		if (binaryString.length() % splitSize == 0) {
			int index = 0;
			int position = 0;

			byte[] resultByteArray = new byte[binaryString.length() / splitSize];
			final StringBuilder text = new StringBuilder(binaryString);

			while (index < text.length()) {
				String binaryStringChunk = text.substring(index, Math.min(index + splitSize, text.length()));
				Integer byteAsInt = Integer.parseInt(binaryStringChunk, 2);
				resultByteArray[position] = byteAsInt.byteValue();
				index += splitSize;
				position++;
			}
			return resultByteArray;
		} else {
			return null;
		}
	}

	public static byte[] directConvert(final long[] data) {
		final byte[] o = new byte[data.length];
		for (int i = 0; i < data.length; i++) {
			o[i] = (byte) data[i];
		}
		return o;
	}

	public static final byte[] intToByteArray(int value) {
		return new byte[]
			{ (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value };
	}

	public static byte[] combine(byte[]... bytes) {
		int totalLen = 0;
		for (byte[] b : bytes) {
			totalLen += b.length;
		}
		byte[] result = new byte[totalLen];
		int indexLen = 0;
		for (byte[] b : bytes) {
			System.arraycopy(b, 0, result, indexLen, b.length);
			indexLen += b.length;
		}
		return result;
	}

	public static int getBit(final byte[] data, final int pos) {
		final int posByte = pos / 8;
		final int posBit = pos % 8;
		final byte valByte = data[posByte];
		final int valInt = valByte >> (8 - (posBit + 1)) & 0x0001;
		return valInt;
	}

	public static boolean hasNewLine(final byte[] buf) {
		if (buf == null || buf.length == 0)
			return false;
		for (final byte b : buf)
			if (b == '\r' || b == '\n')
				return true;
		return false;
	}

	public static int indexOfEither(final byte[] buf, final char... chars) {
		if (buf == null || buf.length == 0 || chars == null || chars.length == 0)
			return -1;
		for (int idx = 0; idx < buf.length; idx++) {
			if (ArrayUtils._contains(chars, (char) buf[idx]))
				return idx;
		}
		return -1;
	}
}
